Skip to content

fix: resolve controlling TTY for hook processes without /dev/tty#51

Open
Rladmsrl wants to merge 1 commit into
warpdotdev:mainfrom
Rladmsrl:fix/resolve-controlling-tty-for-hooks
Open

fix: resolve controlling TTY for hook processes without /dev/tty#51
Rladmsrl wants to merge 1 commit into
warpdotdev:mainfrom
Rladmsrl:fix/resolve-controlling-tty-for-hooks

Conversation

@Rladmsrl
Copy link
Copy Markdown

@Rladmsrl Rladmsrl commented May 18, 2026

Problem

Claude Code spawns hook processes without a controlling terminal. Both
warp-notify.sh scripts emit the OSC 777 sequence with printf … > /dev/tty,
which fails in that context — so Warp never receives any cli-agent event
(no notifications, no status, no footer).

Diagnosis

Instrumenting warp-notify.sh to record its runtime context when invoked by a
real hook (UserPromptSubmit / PostToolUse / Stop):

=== warp-notify.sh — invoked by a Claude Code hook ===
tty:                     not a tty          # hook has no controlling terminal
parent process:          /bin/bash          # ... and neither does its parent
WARP_CLI_AGENT_PROTOCOL_VERSION=1
should_use_structured:   YES                # the plugin wants to emit
/dev/tty:                Device not configured
printf > /dev/tty:       exit=1             # OSC 777 silently dropped

should-use-structured passes (Warp advertises protocol support), so the
plugin intends to send — but the /dev/tty write fails with ENXIO
because the hook process is not attached to any controlling terminal, and the
notification is lost.

(The repo is already aware /dev/tty isn't always available — tests/test-hooks.sh
overrides /dev/tty writes "since they'd fail in CI" — but the runtime hook
path was never given the same treatment.)

Fix

New shared helper scripts/resolve-tty.sh: walk up the process tree from
$PPID to the first ancestor that still has a controlling tty (the claude
process / its parent shell) and write the OSC sequence to that device node.
Falls back to /dev/tty when no ancestor tty is found, so the change is
strictly additive — environments where /dev/tty already works are unaffected.

Both scripts/warp-notify.sh and scripts/legacy/warp-notify.sh route through it.

Testing

  • bash -n on all three scripts.
  • With the diagnostic above: beforeprintf exits 1, nothing reaches the
    terminal; after — the hook resolves the real PTY (/dev/ttysNNN), the
    printf succeeds (exit 0), and the terminal receives and parses the
    cli-agent OSC 777 event.
  • Reproduced on macOS with Claude Code v2.1.143. The root cause is in how
    Claude Code spawns hook processes (no controlling terminal) — independent of
    the terminal app — so it should affect current Claude Code generally;
    maintainers, please confirm against your environment.

Claude Code spawns hook processes without a controlling terminal, so
`printf ... > /dev/tty` fails with "Device not configured" (ENXIO) and
the OSC notification is silently dropped — Warp never receives the
cli-agent event (no notifications, status, or footer ever appear).

Add a shared resolve-tty.sh helper that walks up the process tree to an
ancestor (the claude process or its parent shell) that still has a
controlling tty, and writes the OSC sequence to that device node.
Falls back to /dev/tty when no ancestor tty is found, so behavior is
unchanged in environments where /dev/tty already works.

Applies to both the structured (warp-notify.sh) and legacy
(legacy/warp-notify.sh) notification paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant